﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Xml.Linq;
using System.Windows.Threading;
using RoMBot;
using RoMBot.Database;
using RoMBot.Waypoints;

namespace RomBotUI
{
    /// <summary>
    /// Interaction logic for VisualMap.xaml
    /// </summary>
    public partial class VisualMap : Window
    {
        DispatcherTimer dispatcherTimer = new DispatcherTimer();
        PointCollection myPointCollection = new PointCollection();
        Point firstPoint = new Point();
        //Map.Map map = new Map.Map();
        readonly DispatcherTimer drawTimer = new DispatcherTimer();

        public VisualMap()
        {
            InitializeComponent();
            //this.Visualmap.Draw();
            DibujarPuntos();
            this.MouseLeftButtonUp += CvsOnMouseLeftButtonUp;
            this.PreviewMouseRightButtonUp += CvsOnMouseRightButtonUp;
            this.PreviewMouseWheel += OnPreviewMouseWheel;

            drawTimer.Tick += DrawTimerOnTick;
            drawTimer.Interval = TimeSpan.FromMilliseconds( 1000 );
            drawTimer.Start();
        }

        private void DrawTimerOnTick( object sender, EventArgs eventArgs )
        {
            //Dispatcher.BeginInvoke( new Action( this.Visualmap.Draw ) );
            Dispatcher.BeginInvoke( new Action( this.DibujarPuntos ) );
        }

        private void OnPreviewMouseWheel( object sender, MouseWheelEventArgs e )
        {
            //var escalar = e.Delta > 0 ? 0.05 : -0.05;
            //map.Escala.ScaleX += escalar;
            //map.Escala.ScaleY += escalar;
            //var pt = e.GetPosition( ( UIElement ) sender );
            //if ( e.Delta > 0 )
            //{
            //    //Bot.Debug( "Scaling up." );
            //    ImageScale.ScaleX += 0.25;
            //    ImageScale.ScaleY += 0.25;
            //    //map.Escala.ScaleX += 0.05;
            //    //map.Escala.ScaleY += 0.05;                
            //}
            //else
            //{
            //    //Bot.Debug( "Scaling down." );
            //    ImageScale.ScaleX -= 0.25;
            //    ImageScale.ScaleY -= 0.25;
            //    //map.Escala.ScaleX -= 0.05;
            //    //map.Escala.ScaleY -= 0.05;
            //}
            //ImageScale.CenterX = pt.X;
            //ImageScale.CenterY = pt.Y;
        }

        private void CvsOnMouseRightButtonUp( object sender, MouseButtonEventArgs e )
        {
            Borderzoom.Reset();
            //if ( this.Visualmap.Escala.ScaleX <= 0.055 )
            //    return;
            //var pt = e.GetPosition( ( UIElement ) sender );
            //Bot.Debug( "Scaling down." );
            //this.Visualmap.Escala.ScaleX -= 0.05;
            //this.Visualmap.Escala.ScaleY -= 0.05;

            //Title = "Visual Map - Scale: " + this.Visualmap.Escala.ScaleX;
        }

        private void CvsOnMouseLeftButtonUp( object sender, MouseButtonEventArgs e )
        {
            //if ( this.Visualmap.Hits.Count > 0 )
            //{
            //    foreach ( var hit in this.Visualmap.Hits )
            //    {
            //        Bot.Debug( hit );
            //    }
            //}
            //var pt = e.GetPosition( ( UIElement ) sender );
            //var pt2 = this.TransformToVisual( this.Visualmap.Visual ).Transform( pt );
            //Bot.Debug( string.ItemLinkFormat( "Punto2: {0}", pt2 ) );
            //map.IsHitTest( pt2 );
        }

        private bool first = true;
        static readonly StreamGeometry StreamGeometry = new StreamGeometry();
        readonly ObjectsTable surround = new ObjectsTable();
        private static readonly Pen Trazo = new Pen( Brushes.Blue, 2 );
        readonly DrawingVisual visual = new DrawingVisual();
        private void DibujarPuntos()
        {
            var currentPoint = new Point();
            if ( !( RadarMode.IsChecked ?? false ) )
            {
                //var currentPoint = new Point( Math.Abs( ( double ) Bot.Instancia.WaypointList.Current.X ), Math.Abs( ( double ) Bot.Instancia.WaypointList.Current.Z ) );
                currentPoint = new Point( Bot.Instancia.WaypointList.Current.X, -Bot.Instancia.WaypointList.Current.Z );

                var listModified = currentPoint != firstPoint && this.myPointCollection.All( p => p != currentPoint );
                if ( first || listModified )
                {
                    myPointCollection.Clear();
                    Monitor.Enter( Bot.Instancia.CurrentWaypointList.Waypoints );
                    var lista = new Waypoint[ Bot.Instancia.CurrentWaypointList.Waypoints.Count ];
                    Bot.Instancia.CurrentWaypointList.Waypoints.CopyTo( lista );
                    Monitor.Exit( Bot.Instancia.CurrentWaypointList.Waypoints );

                    foreach ( var punto in lista )
                    {
                        //myPointCollection.Add( new Point( Math.Abs( ( double ) punto.X ), Math.Abs( ( double ) punto.Z ) ) );
                        myPointCollection.Add( new Point( punto.X, -punto.Z ) );
                    }
                    firstPoint = myPointCollection.First();
                    myPointCollection.RemoveAt( 0 );

                    StreamGeometry.Clear();
                    using ( var geometryContext = StreamGeometry.Open() )
                    {
                        geometryContext.BeginFigure( firstPoint, true, false );
                        geometryContext.PolyLineTo( myPointCollection, true, true );
                    }

                    first = false;
                }
            }
            // Draw the polygon visual
            using ( var context = visual.RenderOpen() )
            {
                if ( !( RadarMode.IsChecked ?? false ) )
                {
                    var currWp = Bot.Instancia.WaypointList.Current;
                    var prevWp = Bot.Instancia.WaypointList.GetNextWayPoint( -1 );
                    var prev2Wp = Bot.Instancia.WaypointList.GetNextWayPoint( -2 );
                    context.DrawGeometry( Brushes.Transparent, Trazo, StreamGeometry );
                    //context.DrawLine( new Pen( Brushes.Orange, 2 ), new Point( Math.Abs( prev2Wp.X ), Math.Abs( prev2Wp.Z ) ), new Point( Math.Abs( prevWp.X ), Math.Abs( prevWp.Z ) ) );
                    //context.DrawLine( new Pen( Brushes.GreenYellow, 2 ), new Point( Math.Abs( prevWp.X ), Math.Abs( prevWp.Z ) ), new Point( Math.Abs( currWp.X ), Math.Abs( currWp.Z ) ) );
                    context.DrawLine( new Pen( Brushes.Orange, 2 ), new Point( prev2Wp.X, -prev2Wp.Z ), new Point( prevWp.X, -prevWp.Z ) );
                    context.DrawLine( new Pen( Brushes.GreenYellow, 2 ), new Point( prevWp.X, -prevWp.Z ), new Point( currWp.X, -currWp.Z ) );
                    context.DrawEllipse( Brushes.DarkRed, null, firstPoint, 4d, 4d );
                    foreach ( var point in myPointCollection )
                    {
                        if ( point == currentPoint )
                        {
                            context.DrawEllipse( Brushes.MediumBlue, null, point, 10d, 10d );
                        }
                        else
                        {
                            context.DrawEllipse( Brushes.DarkRed, null, point, 4d, 4d );
                        }
                    }
                }
                //context.DrawEllipse( Brushes.DarkGreen, null, new Point( Math.Abs( ( double ) Bot.Player.X ), Math.Abs( ( double ) Bot.Player.Z ) ), 7d * pointScale, 7d * pointScale );
                context.DrawEllipse( Brushes.DarkGreen, null, new Point( Bot.Player.X, -Bot.Player.Z ), 7d * pointScale, 7d * pointScale );
                surround.Update();
                var dist = DrawDistance.Value;
                if ( ( ShowMobs.IsChecked ?? false ) || ( RadarMode.IsChecked ?? false ) )
                {
                    var mobs = surround.Where( o => o.Type == ObjectType.Monster && RoMBot.Helpers.Distance( Bot.Player.X, Bot.Player.Z, o.X, o.Z ) <= dist );
                    foreach ( var mob in mobs )
                    {
                        //context.DrawEllipse( mob.Aggressive ? Brushes.Red : Brushes.DarkGoldenrod, null, new Point( Math.Abs( ( double ) mob.X ), Math.Abs( ( double ) mob.Z ) ), 5d * pointScale, 5d * pointScale );
                        if ( mob.Address == Bot.Player.targetPtr )
                        {
                            context.DrawEllipse( Brushes.Fuchsia, null, new Point( mob.X, -mob.Z ), 5d * pointScale, 5d * pointScale );
                        }
                        else
                        {
                            if ( Bot.Player.AssistPet != null && mob.Name == Bot.Player.AssistPet.Name )
                            {
                                context.DrawEllipse( Brushes.LightBlue, null, new Point( mob.X, -mob.Z ), 3d * pointScale, 3d * pointScale );
                            }
                            else
                            {
                                if ( mob.TargetPtr == Bot.Player.Address )
                                {
                                    context.DrawEllipse( Brushes.Indigo, null, new Point( mob.X, -mob.Z ), 5d * pointScale, 5d * pointScale );
                                }
                                else
                                {

                                    context.DrawEllipse( mob.Aggressive ? ( Math.Abs( mob.Y - Bot.Player.Y ) < 45 ? Brushes.Red : Brushes.Firebrick ) : ( Math.Abs( mob.Y - Bot.Player.Y ) < 45 ? Brushes.DarkGoldenrod : Brushes.DarkSalmon ), null, new Point( mob.X, -mob.Z ), 5d * pointScale, 5d * pointScale );
                                }
                            }
                        }
                    }
                }
                if ( ShowResources.IsChecked ?? false )
                {
                    var recursos = surround.Where( o => o.Type == ObjectType.NpcOrNode && RoMBot.Helpers.Distance( Bot.Player.X, Bot.Player.Z, o.X, o.Z ) <= dist && Database.Nodes.ContainsKey( o.Id ) );
                    foreach ( var rec in recursos )
                    {
                        //context.DrawEllipse( Database.Nodes[ rec.Id ].Type == NodeType.Herb ? Brushes.LawnGreen : Database.Nodes[ rec.Id ].Type == NodeType.Ore ? Brushes.Gold : Brushes.SaddleBrown, null, new Point( Math.Abs( ( double ) rec.X ), Math.Abs( ( double ) rec.Z ) ), 5d * pointScale, 5d * pointScale );
                        context.DrawEllipse( Database.Nodes[ rec.Id ].Type == NodeType.Herb ? Brushes.LawnGreen : Database.Nodes[ rec.Id ].Type == NodeType.Ore ? Brushes.Gold : Brushes.SaddleBrown, null, new Point( rec.X, -rec.Z ), 5d * pointScale, 5d * pointScale );
                    }
                }
                if ( ShowNpcs.IsChecked ?? false )
                {
                    var npcs = surround.Where( o => o.Type == ObjectType.NpcOrNode && RoMBot.Helpers.Distance( Bot.Player.X, Bot.Player.Z, o.X, o.Z ) <= dist && !Database.Nodes.ContainsKey( o.Id ) && !o.Attackable );
                    foreach ( var npc in npcs )
                    {
                        context.DrawEllipse( Brushes.DarkSlateGray, null, new Point( npc.X, -npc.Z ), 5d * pointScale, 5d * pointScale );
                    }
                }
            }
            this.Imagen.Source = new DrawingImage( visual.Drawing );
        }

        void dispatcherTimer_Tick( object sender, EventArgs e )
        {
            DibujarPuntos();
            //canvas1.Children.Remove( playerPolygon );
            ////canvas1.Children.Remove( rec );
            //DrawChar();
            //playerPointCollection.Clear();
            //Bot.Player.UpdateXYZ();
            ////playerPointCollection.Add( new Point( ( ( Bot.Player.X - 0.1 ) / 10000 ) / 550, ( ( Bot.Player.Z - 0.1 ) / 10000 ) / 414 ) );
            ////playerPointCollection.Add( new Point( ( ( Bot.Player.X + 0.1 ) / 10000 ) / 550, ( ( Bot.Player.Z - 0.1 ) / 10000 ) / 414 ) );
            ////playerPointCollection.Add( new Point( ( ( Bot.Player.X + 0.1 ) / 10000 ) / 550, ( ( Bot.Player.Z + 0.1 ) / 10000 ) / 414 ) );
            //playerPointCollection.Add( new Point( Bot.Player.X - 1, Bot.Player.Z - 1 ) );
            //playerPointCollection.Add( new Point( Bot.Player.X + 1, Bot.Player.Z - 1 ) );
            //var point3 = new Point( Bot.Player.X + 1, Bot.Player.Z + 1 );
            //playerPointCollection.Add( point3 );
            //playerPolygon.Points = playerPointCollection;
            //canvas1.Children.Add( playerPolygon );

            //RoMBotDll.Bot.Player.Update();
            //canvas1.Children.Remove( rec );
            //DrawChar( RoMBotDll.Bot.Player.X, RoMBotDll.Bot.Player.Z );
        }

        private void grillaBase_MouseWheel( object sender, MouseWheelEventArgs e )
        {
            //var st = ( MatrixTransform ) image1.RenderTransform;
            //double zoom = e.Delta > 0 ? .2 : -.2;
            //st.Matrix.Scale( zoom, zoom );

            //MatrixTransform TFG2 = ( MatrixTransform ) image1.RenderTransform;
            //double zoom = e.Delta > 0 ? 2 : -2;
            //TFG2.Matrix.Scale( zoom, zoom );
            //TFG2.SetValue(DependencyProperty.

            //st.ScaleX += zoom;
            //st.ScaleY += zoom;
        }

        private Point[] GetPlayerPoly()
        {
            var player = Bot.Player;
            var points = new List<Point>();
            var x = Math.Abs( ( double ) player.X );
            var y = Math.Abs( ( double ) player.Z );
            points.Add( new Point( x - 10f, y - 10f ) );
            points.Add( new Point( x + 10f, y - 10f ) );
            points.Add( new Point( x - 10f, y + 10f ) );
            points.Add( new Point( x + 10f, y + 10f ) );
            return points.ToArray();
        }
        // Return the result of the hit test to the callback. 
        public HitTestResultBehavior MyHitTestResult( HitTestResult result )
        {
            // Retrieve the results of the hit test.
            var intersectionDetail = ( ( GeometryHitTestResult ) result ).IntersectionDetail;

            switch ( intersectionDetail )
            {
                case IntersectionDetail.FullyContains:

                    // Add the hit test result to the list that will be processed after the enumeration.
                    hitResultsList.Add( result.VisualHit );

                    return HitTestResultBehavior.Continue;

                case IntersectionDetail.Intersects:
                    hitResultsList.Add( result.VisualHit );

                    // Set the behavior to return visuals at all z-order levels. 
                    return HitTestResultBehavior.Continue;

                case IntersectionDetail.FullyInside:
                    hitResultsList.Add( result.VisualHit );

                    // Set the behavior to return visuals at all z-order levels. 
                    return HitTestResultBehavior.Continue;

                default:
                    return HitTestResultBehavior.Stop;
            }
        }
        private double pointScale = 3.0d;
        private List<DependencyObject> hitResultsList = new List<DependencyObject>();
        private void ImagenMouseUp( object sender, MouseButtonEventArgs e )
        {
            //// Retrieve the coordinate of the mouse position.
            //var pt = e.GetPosition( ( UIElement ) sender );
            ////var pt2 = VisualTreeHelper.GetOffset( visual );
            //var pt2 = VisualTreeHelper.GetTransform( Imagen ).Transform( pt );

            //var bounds = VisualTreeHelper.GetContentBounds( visual );
            ////HitTestGeometryInVisual( visual, new Point( pt.X + bounds.Left + pt2.X, pt.Y + bounds.Top + pt2.Y ) );

            //// Clear the contents of the list used for hit test results.
            //hitResultsList.Clear();
            //// Expand the hit test area by creating a geometry centered on the hit test point.
            //var expandedHitTestArea = new EllipseGeometry( pt, 10.0, 10.0 );


            ////// Set up a callback to receive the hit test result enumeration.
            ////VisualTreeHelper.HitTest( visual, null,
            ////    new HitTestResultCallback( MyHitTestResult ),
            ////    new PointHitTestParameters( pt ) );

            //// Set up a callback to receive the hit test result enumeration.
            //VisualTreeHelper.HitTest( Imagen, null,
            //    new HitTestResultCallback( MyHitTestResult ),
            //    new GeometryHitTestParameters( expandedHitTestArea ) );
            //// Perform actions on the hit test results list. 
            //if ( hitResultsList.Count > 0 )
            //{
            //    if ( hitResultsList[ 0 ] is Visual )
            //    {
            //        Bot.Debug( "Es visual." );
            //        HitTestGeometryInVisual( hitResultsList[ 0 ] as Visual, new Point( pt.X + bounds.Left + pt2.X, pt.Y + bounds.Top + pt2.Y ) );
            //    }
            //    Bot.Debug( "Number of Visuals Hit: " + hitResultsList.Count );
            //}

            //var pos = e.GetPosition( this.Imagen );
            ////var min = Math.Abs( Bot.Instancia.WaypointList.Waypoints.Min( p => ( double ) p.X ) );
            ////var max = Math.Abs( Bot.Instancia.WaypointList.Waypoints.Max( p => ( double ) p.X ) );
            ////var width = max - min;
            ////var minH = Math.Abs( Bot.Instancia.WaypointList.Waypoints.Min( p => ( double ) p.Z ) );
            ////var maxH = Math.Abs( Bot.Instancia.WaypointList.Waypoints.Max( p => ( double ) p.Z ) );
            ////var height = maxH - minH;
            //if ( e.ChangedButton == MouseButton.Left )
            //{
            //    //pointScale--;
            //    ImageScale.ScaleX++;
            //    ImageScale.ScaleY++;
            //    ImageScale.CenterX = pos.X;
            //    ImageScale.CenterY = pos.Y;
            //    e.Handled = true;
            //}
            //if ( e.ChangedButton == MouseButton.Right )
            //{
            //    if ( ImageScale.ScaleX > 1 )
            //    {
            //        //pointScale++;
            //        ImageScale.CenterX = pos.X;
            //        ImageScale.CenterY = pos.Y;
            //        ImageScale.ScaleX--;
            //        ImageScale.ScaleY--;
            //    }
            //    e.Handled = true;
            //}
            ////Bot.Player.UpdateXYZ();
            ////var x = Math.Abs( ( double ) Bot.Player.X );
            ////var y = Math.Abs( ( double ) Bot.Player.Z );

            ////Bot.Printf( "Click en: {0} Player:{1} está: {2}\n PfS: {3}\nHitTest: {4}", pos, new Point( x / width, y / height ), IsInPolygon( GetPlayerPoly(), Imagen.PointFromScreen( pos ) ), Imagen.PointFromScreen( pos ), VisualTreeHelper.HitTest( visual, pos ) );

            //if ( pointScale < 3.0d )
            //{
            //    pointScale = 3.0d;
            //}
        }

        // Determine if a geometry within the visual was hit. 
        static public void HitTestGeometryInVisual( Visual visual, Point pt )
        {
            // Retrieve the group of drawings for the visual.
            var drawingGroup = VisualTreeHelper.GetDrawing( visual );
            EnumDrawingGroup( drawingGroup, pt );
        }

        // Enumerate the drawings in the DrawingGroup. 
        static public void EnumDrawingGroup( DrawingGroup drawingGroup, Point pt )
        {
            var drawingCollection = drawingGroup.Children;

            // Enumerate the drawings in the DrawingCollection. 
            foreach ( var drawing in drawingCollection )
            {
                // If the drawing is a DrawingGroup, call the function recursively. 
                if ( drawing is DrawingGroup )
                {
                    EnumDrawingGroup( ( DrawingGroup ) drawing, pt );
                }
                else if ( drawing is GeometryDrawing )
                {

                    // Determine whether the hit test point falls within the geometry. 
                    if ( ( ( GeometryDrawing ) drawing ).Geometry.FillContains( pt ) )
                    {
                        Bot.Debug( "Hit test (dentro)" );
                    }
                    if ( ( ( GeometryDrawing ) drawing ).Geometry.StrokeContains( Trazo, pt ) )
                    {
                        Bot.Debug( "Hit test (trazo)" );
                    }
                }
            }

        }

        public static bool IsInPolygon( Point[] poly, Point p )
        {
            Point p1, p2;


            bool inside = false;


            if ( poly.Length < 3 )
            {
                return inside;
            }


            var oldPoint = new Point(
                poly[ poly.Length - 1 ].X, poly[ poly.Length - 1 ].Y );


            for ( int i = 0; i < poly.Length; i++ )
            {
                var newPoint = new Point( poly[ i ].X, poly[ i ].Y );


                if ( newPoint.X > oldPoint.X )
                {
                    p1 = oldPoint;

                    p2 = newPoint;
                }

                else
                {
                    p1 = newPoint;

                    p2 = oldPoint;
                }


                if ( ( newPoint.X < p.X ) == ( p.X <= oldPoint.X )
                    && ( p.Y - ( long ) p1.Y ) * ( p2.X - p1.X )
                    < ( p2.Y - ( long ) p1.Y ) * ( p.X - p1.X ) )
                {
                    inside = !inside;
                }


                oldPoint = newPoint;
            }


            return inside;
        }
    }
}
